SPDX-FileCopyrightText: 2025 Alyssar Dunia & Loïs Anseel SPDX-FileCopyrightText: 2025 AlICe laboratory https://alicelab.be
SPDX-License-Identifier: GPL-3.0-or-later
import bpy
import math
import bmesh
import mathutils
import random
import pprintCLEAN UP SCENE
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete(use_global=False)
bpy.ops.outliner.orphans_purge()OPTIMIZED GEOMETRY CREATION (one mesh subdivided rather than accumulation of individual elements)
def generate_object_from_grid(grid_pattern, layers, obj_name, position=(0, 0, 0)):Dimensions of each cube
cube_size = 0.5Create a new mesh and object
mesh = bpy.data.meshes.new(obj_name)
obj = bpy.data.objects.new(obj_name, mesh)
bpy.context.collection.objects.link(obj)Generate vertices and faces
vertices = []
faces = []
vertex_index = 0
for z in range(layers):
for y, row in enumerate(grid_pattern):
for x, cell in enumerate(row):
if cell == 1: # If the cell is 1, add a cubeDefine 8 vertices of the cube
cube_vertices = [
(x * cube_size, y * cube_size, z * cube_size),
(x * cube_size + cube_size, y * cube_size, z * cube_size),
(
x * cube_size + cube_size,
y * cube_size + cube_size,
z * cube_size,
),
(x * cube_size, y * cube_size + cube_size, z * cube_size),
(x * cube_size, y * cube_size, z * cube_size + cube_size),
(
x * cube_size + cube_size,
y * cube_size,
z * cube_size + cube_size,
),
(
x * cube_size + cube_size,
y * cube_size + cube_size,
z * cube_size + cube_size,
),
(
x * cube_size,
y * cube_size + cube_size,
z * cube_size + cube_size,
),
]Define 6 faces of the cube
cube_faces = [
(
vertex_index,
vertex_index + 1,
vertex_index + 2,
vertex_index + 3,
),
(
vertex_index + 4,
vertex_index + 5,
vertex_index + 6,
vertex_index + 7,
),
(
vertex_index,
vertex_index + 1,
vertex_index + 5,
vertex_index + 4,
),
(
vertex_index + 1,
vertex_index + 2,
vertex_index + 6,
vertex_index + 5,
),
(
vertex_index + 2,
vertex_index + 3,
vertex_index + 7,
vertex_index + 6,
),
(
vertex_index + 3,
vertex_index + 0,
vertex_index + 4,
vertex_index + 7,
),
]Add vertices and faces to the lists
vertices.extend(cube_vertices)
faces.extend(cube_faces)Update vertex index for the next cube
vertex_index += 8Create the mesh from vertices and faces
mesh.from_pydata(vertices, [], faces)
mesh.update()Set object position
obj.location = positiondef create_rotated_structure(
grid_pattern,
num_stacks,
obj_name,
position=(0, 0, 0),
initial_rotation=2,
rotation_increment=2,
increment_factor=0.5,
scale_ones=1.0,
):Grid and object parameters
grid_size = len(grid_pattern[0])
cell_length = 1
cell_height = 0.75
cell_depth = 1Create a new mesh and object
mesh = bpy.data.meshes.new(obj_name)
obj = bpy.data.objects.new(obj_name, mesh)
bpy.context.collection.objects.link(obj)Initialize bmesh for efficient geometry creation
bm = bmesh.new() def create_layer(base_z, rotation_angle):
cos_theta = math.cos(rotation_angle)
sin_theta = math.sin(rotation_angle)
for row, pattern_row in enumerate(grid_pattern):
for col, cell in enumerate(pattern_row):
if cell == 1:
x = (
col * cell_length
- (grid_size * cell_length / 2)
+ (cell_length / 2)
)
y = (
row * cell_depth
- (grid_size * cell_depth / 2)
+ (cell_depth / 2)
)
rotated_x = cos_theta * x - sin_theta * y
rotated_y = sin_theta * x + cos_theta * yCreate a cube and apply scaling for “1”
bmesh.ops.create_cube(
bm,
size=1.0,
matrix=mathutils.Matrix.Translation(
(rotated_x, rotated_y, base_z)
),
)
bmesh.ops.scale(
bm,
verts=bm.verts[-8:],
vec=(
cell_length / 2 * scale_ones,
cell_depth / 2 * scale_ones,
cell_height / 2 * scale_ones,
),
)Create layers
current_increment = rotation_increment
for stack in range(num_stacks):
layer_z = stack * cell_height
angle = math.radians(initial_rotation + current_increment * stack)
create_layer(layer_z, angle)
current_increment += increment_factorFinalize mesh
bm.to_mesh(mesh)
bm.free()Set object position
obj.location = positionDEFINE BINARY PATTERN
pattern = [
random.choice([1, 0]) for _ in range(18)
] # [1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1]
grid_pattern = []
pattern.reverse()
grid_pattern.append(pattern.copy())
pattern.reverse()
for i in range(len(pattern) - 2):
line = []
line.append(pattern[i + 1])
line.extend([0 for _ in range(len(pattern) - 2)])
line.append(pattern[-i - 1])
grid_pattern.append(line.copy())
grid_pattern.append(pattern.copy())
print("Pattern:")
pprint.pprint(grid_pattern)PARAMETER AND EXECUTE OBJECT CREATION Number of layers to stack on Z-axis for the grid object
grid_layers = 100Create grid object
generate_object_from_grid(
grid_pattern, grid_layers, "GridObject", position=(-4.25, -4.25, -0.5)
)Parameters for rotated structure
num_stacks = 180
initial_rotation = 2
rotation_increment = 2
increment_factor = 1
scale_ones = 1Create rotated structure
create_rotated_structure(
grid_pattern,
num_stacks,
"RotatedStructure",
position=(0, 0, 0),
initial_rotation=initial_rotation,
rotation_increment=rotation_increment,
increment_factor=increment_factor,
scale_ones=scale_ones,
)ISOLATE RANDOM PORTION OF TOWER Create a cube for intersection
bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0), scale=(7, 7, 5))Translate the cube randomly along the Z-axis
bpy.ops.transform.translate(value=(0, 0, random.uniform(0, 50)))Select both objects and the cube
grid_object = bpy.data.objects["GridObject"]
rotated_structure = bpy.data.objects["RotatedStructure"]
intersect_cube = bpy.data.objects["Cube"]Ensure both objects are selected
grid_object.select_set(True)
rotated_structure.select_set(True)Add a boolean modifier to both objects
bpy.context.view_layer.objects.active = (
grid_object # Set an object as active for context
)
for obj in [grid_object, rotated_structure]:
bpy.context.view_layer.objects.active = obj
bpy.ops.object.modifier_add(type="BOOLEAN")
obj.modifiers["Boolean"].operation = "INTERSECT"
obj.modifiers["Boolean"].solver = "FAST"
obj.modifiers["Boolean"].object = intersect_cubeApply modifiers to all selected objects at once
bpy.ops.object.select_all(action="DESELECT")
grid_object.select_set(True)
rotated_structure.select_set(True)
bpy.ops.object.convert(target="MESH") # Applies all modifiers for selected objectsDelete the cube
bpy.ops.object.select_all(action="DESELECT")
intersect_cube.select_set(True)
bpy.ops.object.delete(use_global=False)